package com.cw.lang.common.framework.advice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.cw.base.common.model.dto.response.CommonRespDto;
import com.cw.base.common.model.enums.EnumCommonErrMsg;
import com.cw.base.common.model.enums.EnumRpcResult;
import com.cw.lang.common.constants.HttpStatusConstant;
import com.cw.lang.common.enums.EnumResponseType;
import com.cw.lang.common.exception.BizException;
import com.cw.lang.common.exception.CheckException;
import com.cw.lang.common.exception.DBException;
import com.cw.lang.common.exception.SystemException;
import com.cw.lang.common.exception.TimeoutException;
import com.cw.lang.common.framework.config.FrameworkRpcProperties;
import com.cw.lang.common.utils.SequenceTool;
import com.cw.lang.common.utils.StringUtil;
import com.google.common.collect.Range;

import lombok.extern.slf4j.Slf4j;

/**
 * Description:
 * <p>
 * Created on 2017年2月23日
 *
 * @author chenwei
 * @version 1.0
 * @since v1.0
 */
@ControllerAdvice(basePackages = "com.cw")
@Slf4j
public class ExceptionControllerAdvice {

    @Autowired
    private FrameworkRpcProperties frameworkRpcProperties;

    /**
     * 针对{@link BizException}类型的异常，该异常一般为业务抛出的异常，主要用于向前台传输异常信息
     *
     * @param ex 抛出的异常
     * @author chenwei
     */
    @ExceptionHandler(BizException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(BizException ex) {
        log.error(ex.getErrorMessage(), ex);
        return CommonRespDto.builder()
            .responseType(EnumResponseType.FLOW_ERR)
            .parentIdemSerialId(idemSerialId())
            .errorCode(ex.getErrorCode())
            .errorMessage(ex.getErrorMessage())
            .build();

    }

    /**
     * 针对{@link CheckException}类型的异常，该异常一般为校验错误
     *
     * @param ex 抛出的异常
     * @author chenwei
     */
    @ExceptionHandler(CheckException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(CheckException ex) {
        log.error(ex.getErrorMessage(), ex);
        return CommonRespDto.builder()
            .responseType(EnumResponseType.VALIDATE_ERR)
            .parentIdemSerialId(idemSerialId())
            .errorCode(ex.getErrorCode())
            .errorMessage(ex.getErrorMessage())
            .build();
    }

    /**
     * 针对{@link DBException}类型的异常，该异常一般为数据库异常
     *
     * @param ex 抛出的异常
     * @author chenwei
     */
    @ExceptionHandler(DBException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(DBException ex) {
        log.error(ex.getErrorMessage(), ex);
        return CommonRespDto.builder()
            .responseType(EnumResponseType.SYS_ERR)
            .parentIdemSerialId(idemSerialId())
            .errorCode(ex.getErrorCode())
            .errorMessage(ex.getErrorMessage())
            .build();
    }

    /**
     * 针对{@link TimeoutException}类型的异常，该异常一般为rpc超时异常
     *
     * @param ex 抛出的异常
     * @author chenwei
     */
    @ExceptionHandler(TimeoutException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(TimeoutException ex) {
        log.error(ex.getErrorMessage(), ex);
        return CommonRespDto.builder()
            .httpStatus(HttpStatusConstant.OK)
            .rpcResult(EnumRpcResult.TIMEOUT_ERR)
            .responseType(EnumResponseType.SYS_ERR)
            .parentIdemSerialId(idemSerialId())
            .build();
    }

    /**
     * 除了基础类型之外的异常，该异常一般为内部实现出错时会抛出的异常，例如NullPointerException等等，非业务逻辑异常
     *
     * @param ex 抛出的异常
     * @author chenwei
     */
    @ExceptionHandler(SystemException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(SystemException ex) {
        log.error(ex.getErrorMessage(), ex);
        CommonRespDto.CommonRespDtoBuilder builder = CommonRespDto.builder()
            .httpStatus(ex.getHttpStatus())
            .responseType(EnumResponseType.SYS_ERR)
            .errorCode(ex.getErrorCode())
            .errorMessage(ex.getErrorMessage())
            .parentIdemSerialId(idemSerialId());
        // 如果是rpc成功
        if (Range.closed(HttpStatusConstant.SUCCESS_MIN, HttpStatusConstant.SUCCESS_MAX)
            .contains(ex.getHttpStatus())) {
            builder.rpcResult(EnumRpcResult.SUCCESS);
        }
        // 如果rpc失败
        else {
            // 是超时异常
            if (frameworkRpcProperties.getRpc()
                .isTimeout(ex.getHttpStatus())) {
                builder.rpcResult(EnumRpcResult.TIMEOUT_ERR);
            }
            // 非超时异常
            else {
                builder.rpcResult(EnumRpcResult.FAIL);
            }
        }
        return builder.build();
    }

    /**
     * 除了基础类型之外的异常，该异常一般为内部实现出错时会抛出的异常，例如NullPointerException等等，非业务逻辑异常
     *
     * @param ex 抛出的异常
     * @author
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public CommonRespDto handlerError(Exception ex) {
        if (StringUtil.isBlank(ex.getMessage())) {
            log.error("系统抛出非业务异常，message未设置", ex);
        } else {
            log.error("系统抛出非业务异常，message: " + ex.getMessage(), ex);
        }
        return CommonRespDto.builder()
            .responseType(EnumResponseType.SYS_ERR)
            .parentIdemSerialId(idemSerialId())
            .errorCode(EnumCommonErrMsg.SYSTEM_ERR.getCode())
            // 如果Exception#getMessage为空，则使用自己定义的message
            .errorMessage(
                StringUtil.isBlank(ex.getMessage()) ? EnumCommonErrMsg.SYSTEM_ERR.getDescription() : ex.getMessage())
            .build();
    }

    /**
     * 幂等流水号
     *
     * @return 幂等流水号
     */
    private String idemSerialId() {
        return SequenceTool.nextId();
    }

}
